#include "N1Socket.h"
#include "N1Log.h"
#ifndef WINDOWS
#include <unistd.h>
#endif

#ifdef __WIN32__

uint32 WinSockStart() 
{
    // Initialize Winsock.
    WSADATA wsaData;
    return WSAStartup( MAKEWORD(2,2), &wsaData );
}

void WinSockStop() 
{
    WSACleanup();
}

static char gWinSockStarted;

#endif

void N1SocketInitialize()
{
#ifdef __WIN32__
    if (!gWinSockStarted)
    {
        if (WinSockStart())
        {
            N1Log(N1Log_Error, "Winsock initialize failed.");
        }
        gWinSockStarted = 1;
    }
#endif
}

static int32 _N1SocketGetError()
{
#ifdef __WIN32__
    return WSAGetLastError();
#else
    return errno;
#endif
}

N1Socket::N1Socket()
{
    _socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
}

N1Socket::N1Socket(SOCKET s) : _socket(s)
{
}

N1Socket::~N1Socket()
{
    shutdown(_socket, SD_BOTH);
#ifdef WINDOWS
    closesocket(_socket);
#else
    close(_socket);
#endif
    _socket = INVALID_SOCKET;
}

pcstr N1Socket::name() const 
{ 
    return "N1Socket"; 
}

N1Socket* N1Socket::udpSocket()
{
    SOCKET _socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    N1Socket* ret = new N1Socket(_socket);
    ret->autorelease();
    return ret;
}

void N1Socket::bind(const char* name, uint16 port) 
{
	SOCKADDR_IN sockAddr;
	memset(&sockAddr,0,sizeof(sockAddr));

	LPSTR lpszAscii = (LPTSTR)name;
	sockAddr.sin_family = AF_INET;

	if (lpszAscii == NULL)
	{
		sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	}
	else	
    {
		DWORD lResult = inet_addr(lpszAscii);
		if (lResult==INADDR_NONE)	
        {
			N1Log(N1Log_Error, "Error WSAEINVAL in inet_addr.");
			N1ThrowException("N1Socket::bind: inet_addr returned INADDR_NONE.");
		}
		sockAddr.sin_addr.s_addr = lResult;
	}
	sockAddr.sin_port = htons((u_short)port);

	int ret = ::bind(_socket, (SOCKADDR*)&sockAddr, sizeof(sockAddr));

	if(ret==SOCKET_ERROR)
	{
        N1ThrowException1("N1Socket::bind: bind returned with error %u", _N1SocketGetError());
	}
}

void N1Socket::listen(volatile N1Bool *pBreak, 
                      N1DelegatePtr<void, N1Socket*> *p) 
{
    SOCKET newsocket;

    if (::listen(_socket, SOMAXCONN)==0) {
        while(1) 
        {
            fd_set read;
            timeval timeout;

            read.fd_count = 1;
            read.fd_array[0] = _socket;

            timeout.tv_sec  = 0;
            timeout.tv_usec = 100;

            if(pBreak)
            {
                if(*pBreak) 
                    break;
            }

            int ret = ::select(1, &read, NULL, NULL,  &timeout);

            if(ret==SOCKET_ERROR) 
            {
                N1ThrowException1("N1Socket::listen: select returned with error %u", _N1SocketGetError());
            }

            if(ret==0) continue;

            newsocket = accept(_socket, NULL, NULL);

            if(p)
            {
                p->invoke(new N1Socket(newsocket));
            }
        }
    } 
    else 
    {
        N1ThrowException1("N1Socket::listen: listen returned with error %u", _N1SocketGetError());
    }
}

void N1Socket::connect(pcstr addr, uint16 port) {
    struct sockaddr_in name; 

    setReuseAddr(true);
    setBlockMode(true); 

	memset(&name, 0, sizeof(name));

    name.sin_family = AF_INET;
    name.sin_port   = htons(port);
    name.sin_addr.s_addr = inet_addr(addr);

	if(name.sin_addr.s_addr == INADDR_NONE) {
		LPHOSTENT lphost;
		lphost = gethostbyname(addr);
		if (lphost != NULL)
		{
			name.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr;
		}
		else 
        {
            setBlockMode(false); 
            N1ThrowException1("N1Socket::connect: gethostbyname returned with error %u", _N1SocketGetError());
		}
	}

    int ret = ::connect(_socket, (struct sockaddr*)&name, sizeof(name));

    setBlockMode(false); 

    if(ret==SOCKET_ERROR)
    {
        N1ThrowException1("N1Socket::connect: connect returned with error %u", _N1SocketGetError());
    }
}

bool N1Socket::setBlockMode(bool bBlock) 
{
    u_long bNonBlock = !bBlock;
    return (ioctlsocket(_socket, FIONBIO, (DWORD*)&bNonBlock)==0);
}

bool N1Socket::setReuseAddr(bool bOn) {
    int on = bOn;
    return (setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on))==0);
}

void N1Socket::send(const void *data, N1UInt32 size) 
{
    int nSend;
    int nSended = 0;
    const char *buff = (char*)data;
    while(size) {
        nSend = ::send(_socket, buff + nSended, size, 0);
        if(nSend==SOCKET_ERROR) 
        {
            N1ThrowException1("N1Socket::send: send returned with error %u", _N1SocketGetError());
        }
        size   -= nSend;
        nSended += nSend;
    }
}

void N1Socket::recv(void *buff, N1UInt32 size, N1UInt32 *pReaded) 
{
    int rec;
    char *pCurBuf = (char*)buff;
    int len = size;

    if(pReaded)
        *pReaded = 0;

    while(len) {
        rec = ::recv(_socket, pCurBuf, len, 0);

        if(rec==0)
        {
            N1ThrowException("N1Socket::recv: recv returned with connection was closed");
        }
    
        if(rec==SOCKET_ERROR) 
        {
            N1ThrowException1("N1Socket::recv: recv returned with error %u", _N1SocketGetError());
        }

        len     -= rec;
        pCurBuf += rec;
        if(pReaded)
            *pReaded += rec;
    } 
}

void N1Socket::sendto(const void *data, N1UInt32 size, N1NetworkAddress *addr)
{
    int ret = ::sendto(_socket, (const char*)data, size, 0, addr->addr(), addr->size());
    if(ret==SOCKET_ERROR)
    {
        N1ThrowException1("N1Socket::sendto: sendto returned with error %u", _N1SocketGetError());
    }
}

uint32 N1Socket::recvfrom(void *buffer, N1UInt32 size, N1NetworkAddress **addr)
{
    *addr = new N1NetworkAddressIPv4();
    (*addr)->autorelease();
    int len = (*addr)->size();
    int ret = ::recvfrom(_socket, (char*)buffer, size, 0, (*addr)->addr(), &len);
    if(ret==SOCKET_ERROR)
    {
        N1ThrowException1("N1Socket::recvfrom: recvfrom returned with error %u", _N1SocketGetError());
    }
    return ret;
}

/*********************************************************/

N1NetworkAddressIPv4::N1NetworkAddressIPv4(const char* name, uint16 port) : m_error(0)
{
	memset(&m_addr, 0, sizeof(m_addr));

    m_addr.sin_family = AF_INET;
    m_addr.sin_port   = htons(port);
    m_addr.sin_addr.s_addr = inet_addr(name);

	if(m_addr.sin_addr.s_addr == INADDR_NONE) 
    {
		LPHOSTENT lphost;
		lphost = gethostbyname(name);
		if (lphost != NULL)
		{
			m_addr.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr;
		}
		else 
        {
            m_error = WSAEINVAL;
		}
	}
}

N1NAME_DEF(N1NetworkAddressIPv4)

/**********************************************************
*********************** TESTS *****************************
**********************************************************/
#ifdef N1LIB_SELFTEST

N1TEST(N1SocketCreate)
{
    N1Socket s;
}

N1TEST_SKIP(N1SocketConnect)
{
    N1Socket s;

    s.connect("microsoft.com", 80);
}


static volatile N1Bool gStop;

void N1SocketTestThread(void*)
{
    N1Socket s;

    N1Thread::sleep(1000);

    s.connect("127.0.0.1", 56789);
}

class Server {
public:
    void onConnect(N1Socket* s)
    {
        gStop = true;
    }
};

N1TEST(N1SocketListen)
{
    N1Socket s;
    Server srv;

    N1Thread::detachNewThread(N1SocketTestThread, NULL);

    s.bind(NULL, 56789);

    s.listen(&gStop, new N1Delegate<Server,void, N1Socket*>(&srv, &Server::onConnect));
}

#define UDP_PORT 12645

void N1SocketUDPTestThread(void*)
{
    N1AutoreleasePool pool;

    N1Socket *s = N1Socket::udpSocket();
    char buff[100];
    N1NetworkAddress *addr = NULL;
    uint32 size;

    s->bind(NULL, UDP_PORT);

    size = s->recvfrom(buff, sizeof(buff), &addr);

    N1CHECK(size, "Error in recvfrom");

    strcpy(buff, "Hi, great programmer!!!!!");

    s->sendto(buff, strlen(buff)+1, addr);
}

N1TEST(N1SocketUDP)
{
    N1AutoreleasePool pool;

    N1Socket *s = N1Socket::udpSocket();

    N1Thread::detachNewThread(N1SocketUDPTestThread, NULL);

    N1Thread::sleep(1000);

    char buff[100];
    N1NetworkAddress *addr = new N1NetworkAddressIPv4("127.0.0.1", UDP_PORT);
    uint32 size;

    strcpy(buff, "Hello!!!!!");

    s->sendto(buff, strlen(buff)+1, addr);

    size = s->recvfrom(buff, sizeof(buff), &addr);

    //printf("%s\n", buff);

    N1CHECK(size, "Error in recvfrom");
}

N1TEST(N1SocketUDPBroadcast)
{
    N1AutoreleasePool pool;

    N1Socket *s = N1Socket::udpSocket();

    N1Thread::detachNewThread(N1SocketUDPTestThread, NULL);

    N1Thread::sleep(1000);

    char buff[100];
    N1NetworkAddress *addr = new N1NetworkAddressIPv4(NULL, UDP_PORT);
    uint32 size;

    strcpy(buff, "Hello!!!!!");

    s->sendto(buff, strlen(buff)+1, addr);

    size = s->recvfrom(buff, sizeof(buff), &addr);

    //printf("%s\n", buff);

    N1CHECK(size, "Error in recvfrom");
}

#endif //N1LIB_TEST
